#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Simple CMS
#
# Copyright (C) 2011-2024 Michael Büsch <m@bues.ch>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import sys
sys.path.insert(0, "/opt/cms/lib/python3/site-packages/")

import os, fcntl, socket, atexit, time

try:
    from cms_cython.socket import *
    from cms_cython import CMS, CMSException
except ImportError as e:
    print("cms-backd: Failed to import cms_cython:", e, file=sys.stderr)
    from cms.socket import *
    from cms import CMS, CMSException

def init_systemd():
    notify_socket = os.getenv("NOTIFY_SOCKET")
    if not notify_socket:
        raise ValueError("env: NOTIFY_SOCKET not set.")
    listen_fds = os.getenv("LISTEN_FDS")
    if not listen_fds:
        raise ValueError("env: LISTEN_FDS not set.")
    listen_fds = int(listen_fds, 10)
    if listen_fds <= 0 or listen_fds > 1:
        raise ValueError("env: Invalid LISTEN_FDS.")

    with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) as sdsock:
        sdsock.connect(notify_socket)
        fd = 3
        for i in range(fd, fd + listen_fds):
            fcntl.fcntl(i, fcntl.F_SETFD, fcntl.FD_CLOEXEC)
        backsock = socket.fromfd(fd, socket.AF_UNIX, socket.SOCK_STREAM)
        sdsock.sendall(b"READY=1")
    return backsock

def run_backd(backsock):
    cms = CMS(domain="", debug=True)
    atexit.register(cms.shutdown)
    while True:
        try:
            conn, addr = backsock.accept()
            msg = recv_message(conn, MAGIC_BACK)
        except Exception as e:
            print(f"cms-backd: Receive failed: {e}", file=sys.stderr)
            continue

        if cms.debug:
            startStamp = time.monotonic()

        status = 200 # Ok
        reply_body = b""
        reply_mime = ""
        extra_headers = []
        try:
            if isinstance(msg, MsgGet):
                cms.domain = msg.host
                protocol = "https" if msg.https else "http"
                reply_body, reply_mime = cms.get(msg.path, msg.query, protocol)
            elif isinstance(msg, MsgPost):
                cms.domain = msg.host
                protocol = "https" if msg.https else "http"
                reply_body, reply_mime = cms.post(msg.path, msg.query, msg.body, msg.body_mime, protocol)
            else:
                raise RuntimeError("Received invalid message.")
        except CMSException as e:
            status = e.httpStatusCode
            reply_body, reply_mime, extra_headers = cms.getErrorPage(e, protocol)

        if cms.debug and "html" in reply_mime:
            delta = time.monotonic() - startStamp
            ms = delta * 1e3
            reply_body += ("\n<!-- generated in %.3f ms -->" % ms).encode("UTF-8", "ignore")

        reply = MsgReply(
            status=status,
            body=reply_body,
            mime=reply_mime,
            extra_headers=extra_headers,
        )
        reply_data = reply.pack()
        try:
            conn.sendall(reply_data)
        except Exception as e:
            print(f"cms-backd: Failed to send reply: {e}", file=sys.stderr)
            continue

if __name__ == "__main__":
    run_backd(init_systemd())

# vim: ts=4 sw=4 expandtab
